#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>

const int default_capacity = 5;

template<class T>
class block_queue
{
public:
    block_queue(int cap = default_capacity) : _cap(cap)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }

    ~block_queue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }

private:
    void lock() { pthread_mutex_lock(&_mtx); }
    void unlock() { pthread_mutex_unlock(&_mtx); }

    void pwait() { pthread_cond_wait(&_p_cond, &_mtx); }
    void cwait() { pthread_cond_wait(&_c_cond, &_mtx); }

    void signalp() { pthread_cond_broadcast(&_p_cond); }
    void signalc() { pthread_cond_broadcast(&_c_cond); }

    bool is_full() { return _q.size() == _cap; }
    bool is_empty() { return _q.empty(); }

public:
    void push(const T& in)
    {
        lock();

        while (is_full())
        {
            pwait();
        }

        _q.push(in);

        // if (_q.size() > _cap / 2)
            signalc();

        unlock();
    }

    void pop(T* out)
    {
        lock();

        while (is_empty())
        {
            cwait();
        }

        *out = _q.front();
        _q.pop();

        // if (_q.size() < _cap / 2)
            signalp();

        unlock();
    }

private:
    std::queue<T> _q;
    int _cap;

    pthread_mutex_t _mtx;
    pthread_cond_t _p_cond;
    pthread_cond_t _c_cond;
};
